Skip to content

feat: cleaning up webhooks#212

Open
pengying wants to merge 1 commit intomainfrom
02-19-feat_cleaning_up_webhooks
Open

feat: cleaning up webhooks#212
pengying wants to merge 1 commit intomainfrom
02-19-feat_cleaning_up_webhooks

Conversation

@pengying
Copy link
Contributor

@pengying pengying commented Feb 19, 2026

TL;DR

Updated webhook event type names to be more descriptive and consistent.

Changes defined in https://docs.google.com/document/d/1YdRboRlrwm0HKmRdyp-lbHZcfYUtJMF5NWk35vd_dZI/edit?tab=t.0

Copy link
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@pengying pengying marked this pull request as ready for review February 19, 2026 15:42
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 19, 2026

Greptile Summary

This PR migrates all webhook event type names from coarse single-word identifiers (e.g., INCOMING_PAYMENT, KYC_STATUS) to a descriptive dot-notation OBJECT.EVENT format (e.g., INCOMING_PAYMENT.PENDING, CUSTOMER.KYC_APPROVED). It also restructures the base webhook payload shape — replacing per-webhook top-level fields (transaction, invitation, bulkCustomerImportJob, etc.) with a unified data envelope and renaming webhookId to id.

Key changes:

  • WebhookType enum expanded from 7 coarse values to 20 granular dot-notation values, allowing consumers to route on type without inspecting data.status
  • BaseWebhook restructured: data added as a required field; discriminator mapping removed (intentionally, since M:N type-to-schema mapping can't be expressed in standard OpenAPI discriminators)
  • Schema renames: KycStatusWebhookCustomerKycWebhook, BulkUploadWebhookRequestBulkUploadWebhook
  • /webhooks/test moved to /sandbox/webhooks/test and re-tagged as Sandbox
  • CustomerKycWebhook.data references only IndividualCustomer — worth verifying whether business customers can also trigger KYC events, as the schema would be incomplete in that case
  • BaseWebhook.data description states the field mirrors the GET endpoint response, but IncomingPaymentWebhook.data augments IncomingTransaction with requestedReceiverCustomerInfoFields which is not part of the GET response
  • Outgoing payment examples use empty placeholders (paymentInstructions: [], rateDetails: {}) and provide no examples for the 5 new OUTGOING_PAYMENT.* event types

Confidence Score: 3/5

  • Safe to merge if CustomerKycWebhook only applies to individual customers; this is a breaking change requiring coordinated backend deployment.
  • The schema and documentation changes are internally consistent and well-structured. The main uncertainty is whether CustomerKycWebhook.data being restricted to IndividualCustomer is intentional (if business KYC events exist, the schema is incorrect). The empty placeholder values in outgoing payment examples also reduce documentation quality. All files (source YAML + bundled openapi.yaml + mintlify docs) appear to be kept in sync.
  • openapi/components/schemas/webhooks/CustomerKycWebhook.yaml — verify whether business customers can trigger CUSTOMER.* events

Important Files Changed

Filename Overview
openapi/components/schemas/webhooks/WebhookType.yaml Enum values updated from coarse single-word identifiers to dot-notation OBJECT.EVENT format; new statuses added (PENDING, PROCESSING, SENT, REFUNDED, EXPIRED, SUBMITTED, etc.)
openapi/components/schemas/webhooks/BaseWebhook.yaml Added data as a required property; moved timestamp after type; removed discriminator mapping; description of data is slightly inaccurate for IncomingPaymentWebhook which adds extra fields beyond GET response
openapi/components/schemas/webhooks/CustomerKycWebhook.yaml New schema replacing KycStatusWebhook; data field references only IndividualCustomer which may miss business/entity KYC events
openapi/components/schemas/webhooks/IncomingPaymentWebhook.yaml Restructured to move transaction data + requestedReceiverCustomerInfoFields into unified data allOf; type narrowed to INCOMING_PAYMENT.* values
openapi/components/schemas/webhooks/OutgoingPaymentWebhook.yaml Restructured with data referencing OutgoingTransaction; type narrowed to 7 new OUTGOING_PAYMENT.* variants
openapi/webhooks/outgoing-payment.yaml Examples updated but completed payment example uses empty placeholder values (paymentInstructions: [], rateDetails: {}) and is missing examples for many of the 7 new event types
openapi/webhooks/incoming-payment.yaml Examples updated for PENDING and COMPLETED; no example added for the new INCOMING_PAYMENT.FAILED type
mintlify/snippets/kyc/kyc-webhooks.mdx Documentation updated to new CUSTOMER.* event types with expanded examples and updated code samples
mintlify/snippets/webhooks.mdx Updated webhook type references in code examples and retry policy table to use new dot-notation pattern; test webhook example updated with new structure

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Grid API Event] -->|Sends| B[BaseWebhook\nid, type, timestamp, data]

    B --> C{type prefix}

    C -->|INCOMING_PAYMENT.*| D[IncomingPaymentWebhook\ndata: IncomingTransaction\n+ requestedReceiverCustomerInfoFields]
    C -->|OUTGOING_PAYMENT.*| E[OutgoingPaymentWebhook\ndata: OutgoingTransaction]
    C -->|CUSTOMER.*| F[CustomerKycWebhook\ndata: IndividualCustomer]
    C -->|ACCOUNT.*| G[AccountStatusWebhook\ndata: InternalAccount + oldBalance]
    C -->|INVITATION.*| H[InvitationClaimedWebhook\ndata: UmaInvitation]
    C -->|BULK_UPLOAD.*| I[BulkUploadWebhook\ndata: BulkCustomerImportJob]
    C -->|TEST| J[TestWebhookRequest\ndata: empty object]

    D --> D1[PENDING\nCOMPLETED\nFAILED]
    E --> E1[PENDING · PROCESSING\nSENT · COMPLETED\nFAILED · REFUNDED\nEXPIRED]
    F --> F1[KYC_APPROVED · KYC_REJECTED\nKYC_SUBMITTED\nKYC_MANUALLY_APPROVED\nKYC_MANUALLY_REJECTED]
    G --> G1[BALANCE_UPDATED]
    H --> H1[CLAIMED]
    I --> I1[COMPLETED · FAILED]
Loading

Last reviewed commit: d2d670f

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch from ac110b3 to 3a66bc6 Compare February 28, 2026 00:19
@github-actions
Copy link
Contributor

github-actions bot commented Feb 28, 2026

✱ Stainless preview builds

This PR will update the grid SDKs with the following commit messages.

kotlin

feat: cleaning up webhooks

openapi

feat(api): move test webhook to sandbox, restructure webhook payloads, update types to dot-notation

python

chore(api): move webhooks resource to sandbox namespace

typescript

feat(api): move sendTest method from webhooks to sandbox.webhooks

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-openapi studio · code · diff

Your SDK build was successful, but this did not represent a regression.
generate ✅

grid-python studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

pip install https://pkg.stainless.com/s/grid-python/605ce9fdd1f90b039da7a6f95b7a05341ff907ab/grid-0.0.1-py3-none-any.whl
grid-kotlin studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

grid-typescript studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

npm install https://pkg.stainless.com/s/grid-typescript/8c311896a33eb1cb5d6502a7e32d892a123ac447/dist.tar.gz

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-03-03 02:42:11 UTC

@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch from 3a66bc6 to 3909f05 Compare February 28, 2026 00:22
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 28, 2026

Additional Comments (3)

openapi/components/schemas/invitations/UmaInvitation.yaml, line 1
Update description text from INVITATION_CLAIMED webhook to INVITATION.CLAIMED webhook to match the new dot-notation naming convention.

      platform once the INVITATION.CLAIMED webhook is received. If the inviter

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


openapi/components/schemas/invitations/UmaInvitationCreateRequest.yaml, line 1
Update description text from INVITATION_CLAIMED webhook to INVITATION.CLAIMED webhook to match the new dot-notation naming convention.

      INVITATION.CLAIMED webhook is received. If the inviter

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


openapi/paths/invitations/invitations_{invitationCode}_claim.yaml, line 1
Update description text from An INVITATION_CLAIMED webhook to An INVITATION.CLAIMED webhook to match the new dot-notation naming convention.

    3. An INVITATION.CLAIMED webhook is triggered to notify the platform that

@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch 2 times, most recently from 12a7548 to 8c37e14 Compare February 28, 2026 20:08
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 28, 2026

Additional Comments (5)

openapi/components/schemas/invitations/UmaInvitation.yaml, line 59
Update webhook name reference from INVITATION_CLAIMED to INVITATION.CLAIMED

      platform once the INVITATION.CLAIMED webhook is received. If the inviter

mintlify/ramps/platform-tools/webhooks.mdx, line 73
Update tab to use new ACCOUNT.BALANCE_UPDATED webhook type and new envelope structure with id, type, timestamp, and data fields. The current example uses the old ACCOUNT_STATUS type and structure.


mintlify/ramps/platform-tools/webhooks.mdx, line 88
Update tab to use new CUSTOMER.KYC_* webhook types (e.g., CUSTOMER.KYC_APPROVED) and new envelope structure. The current example uses the old KYC_STATUS type and structure. See mintlify/snippets/kyc/kyc-webhooks.mdx for the updated structure.


mintlify/ramps/platform-tools/webhooks.mdx, line 248
Update to use new webhook type with dot-notation

if (type === "ACCOUNT.BALANCE_UPDATED") {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


mintlify/snippets/creating-customers/customers.mdx, line 170
Update webhook example to use new CUSTOMER.KYC_APPROVED type and envelope structure with id and data fields. The webhook structure has changed from having top-level fields to having a data object containing the customer resource.

@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch from 8c37e14 to 0845a00 Compare March 2, 2026 20:53
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Additional Comments (5)

mintlify/ramps/platform-tools/webhooks.mdx, line 248
Update webhook type references to new dot-notation format. The old types (OUTGOING_PAYMENT, ACCOUNT_STATUS, KYC_STATUS) should be updated to match the new naming convention:

  • OUTGOING_PAYMENTOUTGOING_PAYMENT.COMPLETED, OUTGOING_PAYMENT.FAILED, etc.
  • ACCOUNT_STATUSACCOUNT.BALANCE_UPDATED
  • KYC_STATUSCUSTOMER.KYC_APPROVED, CUSTOMER.KYC_REJECTED, etc.

Affects lines 17, 41, 50, 66, 75, 81, 178, 214, 248 with tab titles, JSON examples, and code comparisons.


mintlify/ramps/accounts/plaid.mdx, line 121
Update to new webhook type: ACCOUNT_STATUS should be ACCOUNT.BALANCE_UPDATED

  if (webhookPayload.type === 'ACCOUNT.BALANCE_UPDATED' && webhookPayload.account) {

mintlify/ramps/conversion-flows/fiat-crypto-conversion.mdx, line 151
Update to check for new webhook types. Since OUTGOING_PAYMENT is now split into status-specific types, this should check for type.startsWith("OUTGOING_PAYMENT.") or specific statuses like OUTGOING_PAYMENT.COMPLETED

  if (type.startsWith("OUTGOING_PAYMENT.") && transaction.status === "COMPLETED") {

mintlify/ramps/conversion-flows/fiat-crypto-conversion.mdx, line 200
Update to check for new webhook types. Use type.startsWith("OUTGOING_PAYMENT.") to handle all outgoing payment statuses

if (type.startsWith("OUTGOING_PAYMENT.") && transaction.status === "COMPLETED") {

mintlify/ramps/conversion-flows/self-custody-wallets.mdx, line 218
Update to check for new webhook types using prefix match

  if (type.startsWith("OUTGOING_PAYMENT.")) {

Comment on lines +3 to +9
- OUTGOING_PAYMENT.PENDING
- OUTGOING_PAYMENT.COMPLETED
- OUTGOING_PAYMENT.FAILED
- OUTGOING_PAYMENT.REFUNDED
- INCOMING_PAYMENT.PENDING
- INCOMING_PAYMENT.COMPLETED
- INCOMING_PAYMENT.FAILED

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought there'd also be one for PROCESSING? cc @shreyav

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, and EXPIRED too please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i investigated this with claude while making the change and:

┌────────────┬───────────┬────────────────────────────┐
│ Enum │ Resolved? │ Outgoing webhook mapping │
├────────────┼───────────┼────────────────────────────┤
│ CREATED │ No │ (no webhook fired) │
├────────────┼───────────┼────────────────────────────┤
│ PENDING │ No │ (no webhook fired) │
├────────────┼───────────┼────────────────────────────┤
│ PROCESSING │ No │ (no webhook fired) │
├────────────┼───────────┼────────────────────────────┤
│ SENT │ No │ (no webhook fired) │
├────────────┼───────────┼────────────────────────────┤
│ COMPLETED │ Yes │ OUTGOING_PAYMENT.COMPLETED │
├────────────┼───────────┼────────────────────────────┤
│ FAILED │ Yes │ OUTGOING_PAYMENT.FAILED │
├────────────┼───────────┼────────────────────────────┤
│ REJECTED │ Yes │ OUTGOING_PAYMENT.FAILED │
├────────────┼───────────┼────────────────────────────┤
│ REFUNDED │ Yes │ OUTGOING_PAYMENT.REFUNDED │
├────────────┼───────────┼────────────────────────────┤
│ EXPIRED │ Yes │ OUTGOING_PAYMENT.FAILED │
└────────────┴───────────┴────────────────────────────┘

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this output mean? don't we want to send webhooks for processing and expired?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't currently sending webhooks for pending processing sent

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't for pending, but we should for the rest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made the updates

@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch 3 times, most recently from 6c8bf4c to f585358 Compare March 3, 2026 00:55
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 3, 2026

Additional Comments (3)

openapi/components/schemas/webhooks/BaseWebhook.yaml, line 29
Discriminator removed — Stainless SDK webhook_unwrap may not resolve subtypes

The old BaseWebhook had a discriminator.mapping that explicitly mapped each type value to a specific schema (e.g., INCOMING_PAYMENT → IncomingPaymentWebhook). This was needed for the Stainless webhook_unwrap method that still declares discriminator: type in .stainless/stainless.yml.

With the new dot-notation types, multiple enum values map to the same schema — e.g., OUTGOING_PAYMENT.PENDING, OUTGOING_PAYMENT.COMPLETED, OUTGOING_PAYMENT.FAILED, and OUTGOING_PAYMENT.REFUNDED all correspond to OutgoingPaymentWebhook. Without a discriminator mapping, the Stainless generator and OpenAPI validators have no way to resolve a specific type value to the appropriate webhook schema.

Consider adding a discriminator back to BaseWebhook that maps every individual type value to its corresponding schema:

discriminator:
  propertyName: type
  mapping:
    OUTGOING_PAYMENT.PENDING: ./OutgoingPaymentWebhook.yaml
    OUTGOING_PAYMENT.COMPLETED: ./OutgoingPaymentWebhook.yaml
    OUTGOING_PAYMENT.FAILED: ./OutgoingPaymentWebhook.yaml
    OUTGOING_PAYMENT.REFUNDED: ./OutgoingPaymentWebhook.yaml
    INCOMING_PAYMENT.PENDING: ./IncomingPaymentWebhook.yaml
    INCOMING_PAYMENT.COMPLETED: ./IncomingPaymentWebhook.yaml
    INCOMING_PAYMENT.FAILED: ./IncomingPaymentWebhook.yaml
    CUSTOMER.KYC_APPROVED: ./CustomerKycWebhook.yaml
    CUSTOMER.KYC_REJECTED: ./CustomerKycWebhook.yaml
    CUSTOMER.KYC_SUBMITTED: ./CustomerKycWebhook.yaml
    CUSTOMER.KYC_MANUALLY_APPROVED: ./CustomerKycWebhook.yaml
    CUSTOMER.KYC_MANUALLY_REJECTED: ./CustomerKycWebhook.yaml
    ACCOUNT.BALANCE_UPDATED: ./AccountStatusWebhook.yaml
    INVITATION.CLAIMED: ./InvitationClaimedWebhook.yaml
    BULK_UPLOAD.COMPLETED: ./BulkUploadWebhook.yaml
    BULK_UPLOAD.FAILED: ./BulkUploadWebhook.yaml
    TEST: ./TestWebhookRequest.yaml

openapi/components/schemas/webhooks/CustomerKycWebhook.yaml, line 10
data locked to IndividualCustomer — business customers excluded

The data field references IndividualCustomer exclusively. If business customers can also undergo KYC (e.g., receiving CUSTOMER.KYC_APPROVED events), this schema would fail validation for their webhook payloads, and the SDK types would be incorrect.

If only individual customers trigger KYC webhooks this is intentional — but it is worth adding a comment to make that explicit. If business customers can also receive KYC webhooks, consider making data a union:

data:
  oneOf:
    - $ref: ../customers/IndividualCustomer.yaml
    - $ref: ../customers/BusinessCustomer.yaml

openapi/webhooks/outgoing-payment.yaml, line 45
Missing examples for OUTGOING_PAYMENT.PENDING and OUTGOING_PAYMENT.REFUNDED

The OutgoingPaymentWebhook schema exposes four event types (PENDING, COMPLETED, FAILED, REFUNDED), but this file only has examples for COMPLETED and FAILED. Consumers discovering the API through the docs will have no example payload to reference when handling PENDING approval flows or refund events. Consider adding examples for the two missing types.

…ation types

- Adopt OBJECT.EVENT dot-notation for all webhook types (e.g., OUTGOING_PAYMENT.COMPLETED)
- Wrap all webhook data under a unified `data` key (Stripe-style full resource embed)
- Rename webhook files to match resource-event pattern
- Move test webhook endpoint to /sandbox/webhooks/test
- Update documentation and examples to match new schema
@pengying pengying force-pushed the 02-19-feat_cleaning_up_webhooks branch from f585358 to d2d670f Compare March 3, 2026 02:37
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 3, 2026

Additional Comments (3)

openapi/components/schemas/webhooks/CustomerKycWebhook.yaml, line 5
data typed as IndividualCustomer only

CustomerKycWebhook.data is set to $ref: ../customers/IndividualCustomer.yaml, but the CUSTOMER.* event types (especially CUSTOMER.KYC_APPROVED/REJECTED) would logically apply to all customer types. If business customers can also undergo KYC/KYB verification and trigger these webhooks, the schema would be incorrect — consumers of this webhook for business customers would receive a payload that doesn't match the declared schema.

If business customers are never subject to these webhooks, a comment clarifying this constraint would prevent confusion. If they are, consider a oneOf/anyOf union:

data:
  oneOf:
    - $ref: ../customers/IndividualCustomer.yaml
    - $ref: ../customers/BusinessCustomer.yaml

openapi/components/schemas/webhooks/BaseWebhook.yaml, line 22
data description is inaccurate for IncomingPaymentWebhook

The data field description says "Contains the full resource as the corresponding GET endpoint would return it." However, IncomingPaymentWebhook.data is an allOf of IncomingTransaction plus requestedReceiverCustomerInfoFields — a field that is not part of the GET /transactions/{id} response. Developers relying on this description to understand the shape of data in an INCOMING_PAYMENT.PENDING event could be misled.

Consider adjusting the description to acknowledge that the data object may be augmented with webhook-specific fields:

data:
  type: object
  description: >-
    The primary resource object associated with this event. For most event types, 
    this mirrors the corresponding GET endpoint response, but may include 
    additional webhook-specific fields (e.g., requestedReceiverCustomerInfoFields 
    on INCOMING_PAYMENT.PENDING).

openapi/webhooks/outgoing-payment.yaml, line 88
Completed payment example has empty placeholder values

The completed outgoing payment example now contains paymentInstructions: [] and rateDetails: {}, replacing the previous version which showed realistic structured data. These empty values provide no guidance to developers on what to expect in a real webhook payload. The previous example with actual accountOrWalletInfo objects was more useful for integration.

Additionally, the 7 new OUTGOING_PAYMENT.* event types (PENDING, PROCESSING, SENT, REFUNDED, EXPIRED) have no examples at all. Similarly, INCOMING_PAYMENT.FAILED has no example in incoming-payment.yaml. Given the expanded set of event types, adding at least representative examples for the new states (like OUTGOING_PAYMENT.REFUNDED) would significantly improve developer experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants